package com.xci.sys.service;

import com.xci.core.annotation.Log;
import com.xci.core.annotation.Valid;
import com.xci.core.base.BaseService;
import com.xci.core.base.CodeValue;
import com.xci.core.base.RestMessage;
import com.xci.core.domain.HistoryInfo;
import com.xci.core.helper.CacheHelper;
import com.xci.core.helper.ConvertHelper;
import com.xci.core.helper.Helper;
import com.xci.core.internal.Const;
import com.xci.sys.dao.ParamDao;
import com.xci.sys.entity.SysParam;
import com.xci.sys.filter.ParamFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.validation.constraints.NotBlank;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 参数服务
 */
@Service
@Slf4j
public class ParamService extends BaseService {
    @Resource
    private ParamDao paramDao;//参数数据层对象
    @Resource
    private Cache paramCache;//参数缓存对象

    /**
     * 新建参数
     * @param entity 参数对象
     */
    @Log(module = Const.Module.Param, msg = "新建参数")
    @Transactional(rollbackFor = Exception.class)
    public RestMessage insert(@Valid SysParam entity) {
        return save(entity, true);
    }

    /**
     * 修改参数
     * @param entity 参数对象
     */
    @Log(module = Const.Module.Param, msg = "修改参数")
    @Transactional(rollbackFor = Exception.class)
    public RestMessage update(@Valid SysParam entity) {
        return save(entity, false);
    }

    /**
     * 批量更新参数键值对
     * @param pairs 参数键值对
     */
    @Log(module = Const.Module.Param, msg = "批量保存参数")
    @Transactional(rollbackFor = Exception.class)
    public RestMessage batchUpdate(@Valid List<CodeValue> pairs) {
        for(var pair : pairs) {
            paramDao.updateValueByCode(pair);
            SysParam param = CacheHelper.getCacheValue(paramCache, pair.getCode());
            if(param != null) {
                param.setValue(pair.getValue());
            }
        }
        return RestMessage.success();
    }

    /**
     * 批量保存参数对象
     * @param entities 参数对象集合
     */
    @Log(module = Const.Module.Param, msg = "批量保存参数对象")
    @Transactional(rollbackFor = Exception.class)
    public RestMessage batchSave(@Valid List<SysParam> entities) {
        for(SysParam param : entities) {
            if(Helper.isBlank(param.getId())) {   //无主键,则新增
                save(param, true).ifFailException();
            } else if(!paramDao.existById(param.getId())) {//主键不存在,则新增
                save(param, true).ifFailException();
            } else {//否则修改
                save(param, false).ifFailException();
            }
        }
        return RestMessage.success();
    }

    /**
     * 删除参数
     * @param ids 参数编码字符串
     */
    @Log(module = Const.Module.Param, msg = "删除参数")
    @Transactional(rollbackFor = Exception.class)
    public RestMessage delete(@NotBlank(message = "请指定参数主键字符串") String ids) {
        List<SysParam> list = CacheHelper.getCacheValueList(paramCache);
        for(var id : Helper.splitToArray(ids)) {
            list.stream().filter(p -> p != null && p.getId().equals(id)).findFirst().ifPresent(p -> {
                paramDao.delete(p.getId());
                paramCache.evict(p.getCode());
                saveDeleteHistory(HistoryInfo.builder().pk(p.getId()).before(p).build());
            });
        }
        return RestMessage.success();
    }

    /**
     * 验证参数编码是否存在
     * @param code 参数编码
     * @return 如果存在返回true, 否则返回false
     */
    public Boolean existByCode(@NotBlank(message = "请指定参数编码") String code) {
        return CacheHelper.contains(paramCache, code);
    }

    /**
     * 根据主键查询单个参数
     * @param id 参数主键
     * @return 返回参数对象
     */
    public SysParam selectById(@NotBlank(message = "请指定参数主键") String id) {
        return paramDao.selectById(id);
    }

    /**
     * 根据编码查询单个参数
     * @param code 参数编码
     * @return 返回参数对象
     */
    public SysParam selectByCode(@NotBlank(message = "请指定参数编码") String code) {
        return CacheHelper.getCacheValue(paramCache, code, () -> paramDao.selectByCode(code));
    }

    /**
     * 根据编码查询单个参数值,如果指定编码在缓存中没有,则去数据库查询
     * @param code 参数编码
     * @param defaultValue 如果没有找到指定编码的参数,则返回此默认值
     * @return 返回参数对象
     */
    public String selectValueByCode(@NotBlank(message = "请指定参数编码") String code, String defaultValue) {
        SysParam param = CacheHelper.getCacheValue(paramCache, code, () -> paramDao.selectByCode(code));
        if(param != null && Helper.isNotBlank(param.getValue())) return param.getValue();
        return defaultValue;
    }

    /**
     * 从缓存中根据编码查询单个参数值,如果指定编码在缓存中没有,则返回默认值
     * @param code 参数编码
     * @param defaultValue 如果没有找到指定编码的参数,则返回此默认值
     * @return 返回参数对象
     */
    public Object getValueByCode(@NotBlank(message = "请指定参数编码") String code, Object defaultValue) {
        SysParam param = CacheHelper.getCacheValue(paramCache, code);
        if(param != null && Helper.isNotBlank(param.getValue())) return param.getValue();
        return defaultValue;
    }

    /**
     * 从缓存中根据编码查询单个参数值(字符串类型),如果指定编码在缓存中没有,则返回默认值
     * @param code 参数编码
     * @param defaultValue 如果没有找到指定编码的参数,则返回此默认值
     * @return 返回参数对象
     */
    public String getStringValueByCode(@NotBlank(message = "请指定参数编码") String code, String defaultValue) {
        return ConvertHelper.toStr(getValueByCode(code, defaultValue));
    }

    /**
     * 从缓存中根据编码查询单个参数值(整型),如果指定编码在缓存中没有,则返回默认值
     * @param code 参数编码
     * @param defaultValue 如果没有找到指定编码的参数,则返回此默认值
     * @return 返回参数对象
     */
    public Integer getIntValueByCode(@NotBlank(message = "请指定参数编码") String code, Integer defaultValue) {
        return ConvertHelper.toInt(getValueByCode(code, defaultValue));
    }

    /**
     * 从缓存中根据编码查询单个参数值(长整型),如果指定编码在缓存中没有,则返回默认值
     * @param code 参数编码
     * @param defaultValue 如果没有找到指定编码的参数,则返回此默认值
     * @return 返回参数对象
     */
    public Long getLongValueByCode(@NotBlank(message = "请指定参数编码") String code, Long defaultValue) {
        return ConvertHelper.toLong(getValueByCode(code, defaultValue));
    }

    /**
     * 从缓存中根据编码查询单个参数值(长整型),如果指定编码在缓存中没有,则返回默认值
     * @param code 参数编码
     * @param defaultValue 如果没有找到指定编码的参数,则返回此默认值
     * @return 返回参数对象
     */
    public Boolean getBooleanValueByCode(@NotBlank(message = "请指定参数编码") String code, Boolean defaultValue) {
        return ConvertHelper.toBool(getValueByCode(code, defaultValue));
    }

    /**
     * 查询参数键值对列表
     * @return 返回参数对象列表
     */
    public List<CodeValue> selectPairList() {
        List<SysParam> list = CacheHelper.getCacheValueList(paramCache);
        return list.stream().filter(Objects::nonNull).map(p -> new CodeValue(p.getCode(), p.getValue())).collect(Collectors.toList());
    }

    /**
     * 查询参数列表
     * @param filter 参数过滤条件
     * @return 返回参数对象列表
     */
    public List<SysParam> selectList(ParamFilter filter) {
        return paramDao.selectList(filter);
    }

    /**
     * 刷新参数缓存
     */
    public void refreshCache() {
        paramCache.clear();
        var list = paramDao.selectList(new ParamFilter());
        list.forEach(p -> paramCache.put(p.getCode(), p));
        log.info("刷新系统参数缓存");
    }

    /**
     * 获取参数缓存对象
     * @return 返回参数缓存对象
     */
    public Cache getParamCache() {
        return paramCache;
    }

    /**
     * 保存参数
     * @param entity 参数对象
     * @param created 是否新建
     */
    private RestMessage save(SysParam entity, boolean created) {
        //自动添加主键
        if(created && Helper.isBlank(entity.getId())) {
            entity.setId(Helper.nextIdStr());
        }

        //检查参数编码是否存在
        if(paramDao.existByCode(entity.getCode(), created ? null : entity.getId())) {
            return RestMessage.fail(Helper.format("参数编码[{}]已经存在", entity.getCode()));
        }

        if(created) {// 新建
            paramDao.insert(entity);
            saveInsertHistory(
                    HistoryInfo.builder().pk(entity.getId()).after(paramDao.selectById(entity.getId())).build());
        } else {     //修改
            var old = paramDao.selectById(entity.getId());
            paramDao.update(entity);
            saveUpdateHistory(HistoryInfo.builder().pk(entity.getId()).before(old).after(entity).build());
        }
        paramCache.put(entity.getCode(), entity);
        return RestMessage.success();
    }
}